﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Db8.Data;

namespace Db8.DataAccess
{
    public class Logger
    {

        private static readonly TimeSpan DELAY = new TimeSpan(0, 0, 30);
        private static readonly TimeSpan LOG_EXPIRATION = new TimeSpan(90, 0, 0, 0);
        private static readonly TimeSpan CLEANUP_FREQUENCY = new TimeSpan(1, 0, 0, 0);

        // when application starts [or whenever this class loads], do a cleanup on first flush
        private static DateTime lastCleanup = DateTime.UtcNow - new TimeSpan(2, 0, 0, 0);

        private static Queue<Log> queue = new Queue<Log>();

        public static void LogInfomation(long? UserId, string Location, string Message, string Detail)
        {
            try
            {
                Log log = new Log()
                {
                    LogDt = DateTime.UtcNow,
                    UserId = UserId,
                    Severity = "Information",
                    Location = Location,
                    Message = Message,
                    Detail = Detail
                };
                LogEntry(log);
            }
            catch { }
        }
        public static void LogWarning(long? UserId, string Location, string Message, string Detail)
        {
            try
            {
                Log log = new Log()
                {
                    LogDt = DateTime.UtcNow,
                    UserId = UserId,
                    Severity = "Warning",
                    Location = Location,
                    Message = Message,
                    Detail = Detail
                };
                LogEntry(log);
            }
            catch { }
        }
        public static void LogError(long? UserId, string Location, string Message, string Detail)
        {
            try
            {
                Log log = new Log()
                {
                    LogDt = DateTime.UtcNow,
                    UserId = UserId,
                    Severity = "Error",
                    Location = Location,
                    Message = Message,
                    Detail = Detail
                };
                LogEntry(log);
            }
            catch { }
        }
        public static void LogException(long? UserId, Exception ex)
        {
            try
            {
                Log log = new Log()
                {
                    LogDt = DateTime.UtcNow,
                    UserId = UserId,
                    Severity = "Error",
                    Location = ex.TargetSite.DeclaringType.Name + "." + ex.TargetSite.Name,
                    Message = ex.GetType().Name + ": " + ex.Message,
                    Detail = ex.StackTrace
                };
                LogEntry(log);
            }
            catch { }
        }
        private static void LogEntry(Log log)
        {
            if (log.Severity == null)
                log.Severity = "";
            if (log.Severity.Length > 15)
                log.Severity = log.Severity.Substring(0, 15);

            if (log.Location == null)
                log.Location = "";
            if (log.Location.Length > 256)
                log.Location = log.Location.Substring(0, 256);

            if (log.Message == null)
                log.Message = "";
            if (log.Message.Length > 256)
                log.Message = log.Message.Substring(0, 256);

            if (log.Detail == null)
                log.Detail = "";
            if (log.Detail.Length > 4500)
                log.Detail = log.Detail.Substring(0, 4500);
            
            ThreadPool.QueueUserWorkItem(SaveLogEntry, log);

            //ThreadPool.QueueUserWorkItem(EnqueueLogEntry, log);
        }

        private static void SaveLogEntry(object log)
        {
            try
            {
                using (Db8Context db = new Db8Context())
                {
                    db.Log.Add((Log)log);

                    // delete old records based on CLEANUP_FREQUENCY, LOG_EXPIRATION, and lastCleanup
                    if (lastCleanup < (DateTime.UtcNow - CLEANUP_FREQUENCY))
                    {
                        lastCleanup = DateTime.UtcNow;
                        DateTime expirationDt = DateTime.UtcNow - LOG_EXPIRATION;
                        db.Log.Where(le => le.LogDt < expirationDt).ToList().ForEach(le => db.Log.Remove(le));
                    }

                    db.SaveChanges();
                }
            }
            catch { }
        }

        private static void EnqueueLogEntry(object log)
        {
            lock (queue)
            {
                queue.Enqueue((Log)log);
                // did I just add first item to queue? If so, start timer to flush queue
                // otherwise, the timer should already be set preparing to flush
                // if it's in the middle of flushing, queue should be locked, so se can't be here
                // if here when flush is ready to start, it will wait until this lock ends
                if (queue.Count == 1)
                    ThreadPool.QueueUserWorkItem(FlushQueue);
            }
        }

        private static void FlushQueue(object o)
        {
            Thread.Sleep(DELAY);
            lock (queue)
            {
                if (queue.Count >= 0)
                {
                    try
                    {
                        using (Db8Context db = new Db8Context())
                        {
                            while (queue.Count > 0)
                            {
                                db.Log.Add(queue.Dequeue());
                            }

                            // delete old records based on CLEANUP_FREQUENCY, LOG_EXPIRATION, and lastCleanup
                            if (lastCleanup < (DateTime.UtcNow - CLEANUP_FREQUENCY))
                            {
                                lastCleanup = DateTime.UtcNow;
                                DateTime expirationDt = DateTime.UtcNow - LOG_EXPIRATION;
                                db.Log.Where(le => le.LogDt < expirationDt).ToList().ForEach(le => db.Log.Remove(le));
                            }

                            db.SaveChanges();
                        }
                    }
                    catch { }
                }
            }
        }



    }
}
